對多數人來說,JSX 應該已經是熟透透的老朋友了吧!但對我而言又是一個全新的學習區塊,就跟認識一位新朋友一樣,我也準備來慢慢熟悉 JSX 啦~
在上一篇認識 React Component 時,我知道 Component 可以透過 return 回傳要顯示的畫面:
// App.tsx
function App() {
return (
<div className="app">
<h1>Todo List</h1>
<TodoForm onAdd={addTodo} />
<TodoList
todos={todos}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
</div>
)
}
但每次看到這種寫法,還是忍不住覺得真的蠻酷的。
為什麼 HTML 可以直接寫在 JavaScript 裡?
原來 return 後面這段長得很像 HTML 的語法稱為 JSX。
JSX 常被稱為 JavaScript XML,它是 JavaScript 的語法擴充,讓我們可以使用類似 HTML 的方式描述使用者介面的外觀。
雖然它長得很像 HTML,裡面卻又能直接使用 JavaScript,第一眼確實讓人覺得有點神奇。不過仔細拆開來看,好像又蠻直觀的。
JSX 的寫法雖然和 HTML 很相似,但它並不是真的 HTML,也不能直接被瀏覽器理解。
在 React 專案執行與打包的過程中,Vite 等工具會先將 JSX 轉換成一般的 JavaScript,再交給 React 建立畫面。
例如:
const title = <h1>Todo List</h1>
這裡的 <h1>Todo List</h1> 並不是一段 HTML 字串,而是用 JSX 描述一個標題元素。
JSX 經過轉換後會成為 JavaScript 可以處理的值,因此它不只可以寫在 return 裡,也可以指定給變數或從函式中回傳。
function App() {
return <h1>Todo List</h1>
}
這也讓我更理解上一篇提到的概念:React Component 是一個負責描述畫面的 function,而 JSX 就是它用來描述畫面的語法。
上一篇曾經留下一個問題:JSX 裡的 {} 代表什麼?
在 JSX 中,大括號 {} 就像是進入 JavaScript 的入口,可以用來放入變數、物件屬性或其他 JavaScript 運算式。
先來看一個簡單的例子:
const title = 'Todo List'
function App() {
return <h1>{title}</h1>
}
這裡的:
{title}
會讀取 title 變數的內容,因此畫面上最後會顯示:
Todo List
在目前的 Todo List 專案中也能看到相同的寫法:
<p className="summary">
已完成 {completedCount} / {todos.length} 項
</p>
這段 JSX 同時包含了固定文字與 JavaScript 的值:
已完成與項是固定文字{completedCount}是已完成的數量{todos.length}是全部待辦事項的數量假設目前共有 3 筆待辦事項,其中 1 筆已完成,畫面就會顯示:
已完成 1 / 3 項
因此,JSX 不只能顯示固定的文字,也可以將 JavaScript 的資料放進畫面。
大括號裡可以放的是 JavaScript「運算式」,也就是能夠產生一個結果的程式碼,例如:
{name}
{todos.length}
{completedCount + 1}
至於專案中比較複雜的 {todos.map(...)} 和條件判斷,會分別留到後面的清單渲染與條件渲染再繼續了解。
JSX 雖然看起來像 HTML,但還是有一些不同的語法規則。
class 要寫成 className在 HTML 中,我們會使用 class 指定 CSS class:
<div class="app"></div>
但是在 JSX 中要改成 className:
<div className="app"></div>
在目前的 App.tsx 中也可以看到:
<div className="app">
<h1>Todo List</h1>
</div>
這一點上一篇介紹 React 的 CSS 時已經稍微提過,現在才知道它也是 JSX 與 HTML 的語法差異之一。
除了 className 之外,JSX 中還有 onClick、onChange 和 onSubmit 這類駝峰式命名的事件屬性。
不過,它們不只是換了一種名稱,還可以接收 JavaScript 函式來處理事件。至於 React 的事件處理和 Vue 的 @click 有什麼差異,就留到後面再仔細研究研究。
JSX 對標籤的語法要求比較嚴格,每一個標籤都必須正確關閉。
有內容的標籤需要同時寫出開始與結束標籤:
<button type="submit">新增</button>
沒有子內容的標籤則可以使用 /> 自我關閉:
<input type="text" />
目前專案中的輸入框也是使用自我關閉的寫法:
<input
type="text"
value={text}
onChange={(event) => setText(event.target.value)}
placeholder="輸入待辦事項..."
/>
自訂 Component 同樣需要正確關閉:
<TodoForm onAdd={addTodo} />
如果標籤沒有正確關閉,程式就會出現語法錯誤。
一個 Component 不能直接回傳多個並列的 JSX 元素。
例如下面的寫法會發生錯誤:
return (
<h1>Todo List</h1>
<TodoForm onAdd={addTodo} />
)
因為 <h1> 和 <TodoForm /> 的外面沒有共同的元素。
目前的 App.tsx 使用 <div> 將所有內容包起來:
return (
<div className="app">
<h1>Todo List</h1>
<TodoForm onAdd={addTodo} />
<TodoList
todos={todos}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
</div>
)
這樣 Component 回傳的內容就只有一個最外層元素。
如果不想為了包住內容而增加一個 <div>,也可以使用 React Fragment:
return (
<>
<h1>Todo List</h1>
<TodoForm onAdd={addTodo} />
</>
)
Fragment 的寫法是:
<>
...
</>
它可以將多個 JSX 元素包在一起,但不會在最後的 HTML 中增加一層多餘的元素。
上一篇已經知道,React Component 的名稱必須以大寫字母開頭。
現在從 JSX 的角度再看一次,React 會根據標籤名稱的大小寫,判斷它代表的是 HTML 元素還是自訂 Component。
小寫開頭會被視為原生 HTML 元素:
<div></div>
<h1></h1>
<input />
<button></button>
大寫開頭則會被視為自訂 Component:
<App />
<TodoForm />
<TodoList />
<TodoItem />
例如 App.tsx 中的這段 JSX:
<div className="app">
<h1>Todo List</h1>
<TodoForm onAdd={addTodo} />
</div>
其中:
<div>與<h1>是原生 HTML 元素<TodoForm />是自己建立的 React Component這部分和上一篇認識 Component 的內容相同,所以這裡就先不重複深入,只要記得 JSX 可以同時組合原生元素與自訂 Component 就可以了。
一般 JavaScript 的單行註解可以寫成:
//這是 JavaScript 註解
但是在 JSX 標籤裡,註解的寫法不太一樣。
目前的 TodoList.tsx 中就有一段 JSX 註解:
<ul className="todo-list">
{/* 用 map 把每一筆 todo 資料轉成一個 TodoItem 元件 */}
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={onToggle}
onDelete={onDelete}
/>
))}
</ul>
JSX 裡的註解要寫成:
{/* 這是 JSX 裡的註解 */}
可以把它拆成兩個部分理解:
{} 進入 JavaScript/* */ 寫區塊註解今天先從最基本的部分認識了 JSX:
return 回傳 JSX{} 將 JavaScript 運算式放進 JSXclassName,而不是 HTML 的 class
{/* ... */}
一開始看到 JSX 時,我的第一個印象是:「這看起來就像是 HTML + JavaScript 的混合體。」
實際從 Todo List 專案的程式碼慢慢拆解後,才發現 JSX 並不是單純地把 HTML 搬進 JavaScript,而是讓我們能用接近 HTML 的方式描述畫面,同時又能透過 {} 使用 JavaScript 的資料。
可以先把 JSX 想成 JavaScript 與使用者介面之間的一座橋:一邊使用 JavaScript 處理資料,一邊使用類似 HTML 的結構描述畫面這樣子的概念。
今天就先到這!
下一篇 Day 07,深入探索 React 的 Props!